package flp.solvers.genetic;

import flp.Layout;
import flp.exceptions.FlowImpossibleException;
import flp.exceptions.LayoutBuildException;
import flp.problem.ProblemInstance;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeSet;

/**
 * Klasa reprezentujaca populacje.
 * 
 * @author Necro i Pipen
 */
public class Population {
    /** Kolekcja Layoutow wchodzacych w sklad populacji. **/
    private TreeSet<Layout> population;

    /**
     * Metoda pobierajaca osobnikow populacji.
     * 
     * @return Zbior layoutow populacji.
     */
    public TreeSet<Layout> getSpeciments()
    {
        return population;
    }

    /** Konstruktor tworzacy pusta populacje. */
    public Population(){
        population=new TreeSet<Layout>();
    }
    
    /** 
     * Metoda statyczna generujaca populacje poczatkowa.
     * 
     * @return Nowa populacja poczatkowa.
     */
    public static Population generatePopulation(GeneticSettings set,ProblemInstance problem) throws LayoutBuildException{
      
      Population result = new Population();
      for(int i=0; i < set.getPopulationCount() ; i++){
          boolean tmp;
          do {
            // szukaj aż znajdziesz unikalny dla populacji layout, potem go dodaj
              Layout tempLayout = Layout.getInstance(problem);
              tmp = result.addLayout(tempLayout);
          } while (tmp != true);
      }
      return result;
    }

    
    /**
     * Metoda dodajaca layout do populacji,
     * sprawdza czy jest tam juz osobnik o takim samym koszcie.
     * 
     * @param lay Dodawany Layout.
     * @return True jezeli nowy layout zostal dodany, false jezeli się powtorzyl.
     */
    public boolean addLayout(Layout lay){
        if (population.contains(lay))
            return false;
        else{
            population.add(lay);
            return true;
        }
    }
    
    /**
     * Metoda zwraca najlepszy layout z danej populacji.
     * 
     * @return Layout o najnizszym koszcie z populacji.
     */  
    public Layout getTop(){       
        return population.first();
    }
    
    /**
     * Metoda zamieniajaca newLay z najgorszym layoutem populacji, jezeli:
     * <ul>
     * <li>koszt nowego layoutu nie powtarza sie w populacji</li>
     * <li>nowy layout ma koszt nizszy niz layout stary</li>
     * </ul>
     * 
     * @param newLay Nowy layout do zamiany.
     * @return True jezeli dokonano zamiany.
     */
     int swapWorst(Layout newLay) throws FlowImpossibleException{
         
        if (!population.contains(newLay)){
            Layout oldWorst=population.pollLast();
            int diff = newLay.getCost(GeneticSolver.getPreferableCostFunction())-oldWorst.getCost(GeneticSolver.getPreferableCostFunction());
            
            if ( diff <= 0 ){
                population.add(newLay);
                return diff;
            } else
                population.add(oldWorst);
            
        }
        
        return 0;
    }
    
     /**
      * Metoda zwraca dwa losowe layouty w populacji, wybrane z okreslonym 
      * prawdopodobienstwem zaleznym od kosztow.
      * 
      * @param rouletteThreshold Prog licznosci populacji, gdzie rozklad P jest rownomierny.
      * @return Tablica nowych Layotow na podmiane.
      */
     public Layout[] getRandomSpeciments(int rouletteThreshold){
         
         Random rand = new Random();
         int indxA, indxB, indxTmp;
         Layout[] result = new Layout[2];
         int popCount = this.getSpeciments().size();
         
         // zbyt mała licznosc żeby korzystac z metody ruletki
         if (popCount < rouletteThreshold){
             indxTmp = rand.nextInt(rouletteThreshold);
             indxA = rand.nextInt(rouletteThreshold);
             
         } else {
             int range = popCount/10;
             //System.out.println(range);
             int rangeProbabl = rand.nextInt(100);
             
             //1 prawdopodobienstwo 30% dla pierwszego przedzialu
             if (rangeProbabl < 30){
                indxTmp = rand.nextInt(range);
                indxA = rand.nextInt(range);
                        
             //2 prawdopodobienstwo 20% dla drugiego przedzialu
             } else if (rangeProbabl < 50){
                indxTmp = rand.nextInt(range)+range;
                indxA = rand.nextInt(range)+range;
                
             //3 prawdopodobienstwo 15% dla trzeciego przedzialu    
             } else if (rangeProbabl < 65){
                indxTmp = rand.nextInt(range)+2*range;
                indxA = rand.nextInt(range)+2*range;
             //4 prawdopodobienstwo 10%
             } else if (rangeProbabl < 75){
                indxTmp = rand.nextInt(range)+2*range;
                indxA = rand.nextInt(range)+2*range;
             //5 prawdopodobienstwo 8%
             }else if (rangeProbabl < 83){
                indxTmp = rand.nextInt(range)+2*range;
                indxA = rand.nextInt(range)+2*range;
             //6 prawdopodobienstwo 6% dla pozostalych 75% osobnikow
             }else if (rangeProbabl < 79){
                indxTmp = rand.nextInt(range)+2*range;
                indxA = rand.nextInt(range)+2*range;
             //7 prawdopodobienstwo 4% dla pozostalych 75% osobnikow
             }else if (rangeProbabl < 94){
                indxTmp = rand.nextInt(range)+2*range;
                indxA = rand.nextInt(range)+2*range;
             //8 prawdopodobienstwo 2% dla pozostalych 75% osobnikow
             }else {
                //300+[700]
                indxTmp = rand.nextInt(7*range)+3*range;
                indxA = rand.nextInt(7*range)+3*range;
             }
        
         }
         
          // warunek by indxB >= indxA
          if (indxTmp >= indxA){
                indxB=indxTmp;
            } else {
                indxB=indxA;
                indxA=indxTmp;
            }
            int i=0;
            //System.out.println(indxA+" :: "+indxB);
            
            for(Iterator<Layout> itL = population.iterator() ; itL.hasNext() ; ){
                Layout current = itL.next();
                
                if (i==indxA){
                    result[0]=current;
                }
                
                if (i==indxB){
                    result[1]=current;
                    break;
                }
                i++;
            } 
       
         return result;
     }
     
     /**
      * Metoda zwracajaca dwa losowe Layouty z populacji.
      * 
      * @return Tablica dwuelementowa zawierajaca wylosowane layouty.
      */        
     public Layout[] getRandomSpeciments(){
         Random rnd = new Random();
         int r = rnd.nextInt(this.getSize());
         Layout [] lays = new Layout[2];
         
         Iterator<Layout> iter = population.iterator();
         for(int i=0; i<r-1; i++)
               iter.next();
         lays[0] = iter.next();
         
         r = rnd.nextInt(this.getSize());
         iter = population.iterator();
         for(int i=0; i<r-1; i++)
               iter.next();
         lays[1] = iter.next();      
         return lays;
     }
     
    /**
     * Metoda liczaca calkowity koszt populacji.
     * 
     * @return Sumaryczny koszt populacji.
     */
    public int getTotalCost() throws FlowImpossibleException{
        int totalcost = 0;
        for(Layout cur : population){
            totalcost +=cur.getCost(GeneticSolver.getPreferableCostFunction());
        }
        return totalcost;
    }

    /**
     * Metoda sprawdzajaca czy spelniony jest warunek stopu.
     * 
     * @return True jesli warunek stopu jest spelniony.
     */
    public boolean checkStopCondition(int newCost, int oldCost, GeneticSolver parentSolver){
      if ( Math.abs(newCost-oldCost) < parentSolver.getCurrentSettings().getEpsilon())
          return true;
      else
          return false;
    }

    @Override
    public String toString() {
        String s = "";
        Iterator<Layout> iter = population.iterator();
        while(iter.hasNext()) {
            s = s + ((Layout)iter.next()).toString() + "\n";
        }
        return s;
    }
    
    public int getSize() {
        return population.size();
    }
    
    /**
     * Funkcja zamienia n% najgorszych Layoutow w populacji na nowe.
     * 
     * @param n Procent Layoutow do wymiany.
     * @param problem Specyfikacja problemu, wedlug ktorego maja byc losowane Layouty.
     * @throws flp.exceptions.LayoutBuildException Wyjatek rzucany, gdy ograniczenia sa za ostre.
     */
    public void swapPoorLayouts(int n, ProblemInstance problem, int generation) throws LayoutBuildException, FlowImpossibleException {
       
        Iterator<Layout> iter = population.iterator();
        int size = population.size();
        int amountToSwap = 0;
        if(n > 100 || n < 0 ) amountToSwap = ((size * 10) / 3 );
        else amountToSwap = ((size * n ) / 100 ) ;
        
        int start = size - amountToSwap;
        for(int i=0; i<start; i++) iter.next();             //przejscie do miejsca gdzie zaczynamy rzez
        while(iter.hasNext()) {                             //rzeznia Layoutow
            iter.remove();
            iter.next();
        }
        
        //WERSJA 1 : wielokrotne losowanie Layoutow i wybieranie najlepszego
       if(generation > 0) {
        TreeSet<Layout> set = new TreeSet<Layout>();
        final int TRIES = 30;
        for(int i=0; i< TRIES; i++) {
            set.add(Layout.getInstance(problem));
        }
        for(int i=0; i<amountToSwap; i++) {
            for(int j=0; j< TRIES; j++) {
                Layout t = Layout.getInstance(problem);
                Layout best = set.pollLast();
                if(t.getCost(GeneticSolver.getPreferableCostFunction()) > best.getCost(GeneticSolver.getPreferableCostFunction())) 
                    set.add(t);
                else
                    set.add(best);
                }
            population.add(set.pollFirst());
            set.add(Layout.getInstance(problem));
        }
       } else {
        // WERSJA 2 - losowanie layoutow AZ znajdziemy lepsze od tych ktore byly na ostatnich 10 pozycjach
        int target = population.last().getCost(GeneticSolver.getPreferableCostFunction());
        System.out.println("popcount: "+ population.size() +" target: " + target);
        for(int i=0; i<amountToSwap; i++) {
            Layout t = Layout.getInstance(problem);
            while ( t.getCost(GeneticSolver.getPreferableCostFunction()) > target) {
                t = Layout.getInstance(problem);
            }
            population.add(t);
        }
       }
    }
 
    
   /** 
    * Metoda statyczna generujaca polepszona randomowymi losowaniami populacje poczatkowa.
    * 
    * @return Nowa populacja poczatkowa.
    */
    public static Population generateCoolPopulation(ProblemInstance problem, GeneticSettings set) throws LayoutBuildException, FlowImpossibleException{
 
        Population result = new Population();
       
        TreeSet<Layout> treeSet = new TreeSet<Layout>();
        final int TRIES = 100;
        for(int i=0; i< TRIES; i++) {
            treeSet.add(Layout.getInstance(problem));
        }
        
        for(int i=0; i < set.getPopulationCount() ; i++) {
            for(int j=0; j< TRIES; j++) {
                Layout t = Layout.getInstance(problem);
                Layout best = treeSet.pollLast();
                if(t.getCost(GeneticSolver.getPreferableCostFunction()) > best.getCost(GeneticSolver.getPreferableCostFunction())) 
                    treeSet.add(t);
                else
                    treeSet.add(best);
            }
         result.population.add(treeSet.pollFirst());
         treeSet.add(Layout.getInstance(problem));
       }
           
        return result;
     }
     
}